home *** CD-ROM | disk | FTP | other *** search
/ Deutsche Edition 1 / Deutsche Edition 1.iso / amok / 081-090 / amok89 / xstat1.12 / xstat.mod < prev    next >
Text File  |  1993-11-04  |  48KB  |  1,558 lines

  1. (***************************************************************************
  2.  :Program.    XStat
  3.  :Author.     Jürgen Weinelt
  4.  :Address.    Zur Kanzel 1, D-8783 Hammelburg, Germany
  5.  :Address.    USENET: jow@sun.rz.uni-wuerzburg.de
  6.  :Address.    USENET: jow@hcast.adsp.sub.org
  7.  :Address.    USENET: jow@hcast.franken.de
  8.  :Version.    $VER: XStat 1.12 (23-Apr-93)
  9.  :Copyright.  Freeware
  10.  :Language.   Modula-2
  11.  :Translator. M2Amiga V4.1
  12.  :History.    1.00 initial release
  13.  :History.    1.01 bug fix: used to guru when xfer = 0 bytes
  14.  :History.    1.02 added peak cps rating
  15.  :History.    1.03 added monthly statistics
  16.  :History.    1.04 switched to DosL.ReadArgs(), fixed nasty current date
  17.  :History.         bug with WB2.1 (problems with locale and "TODAY")
  18.  :History.    1.05 added per host statistics
  19.  :History.    1.06 integrated "per host" statistics into "HOSTNAME" option
  20.  :History.         (pattern matching using dos.library)
  21.  :History.    1.07 bug fix: linked list of Statistics-records was not
  22.  :History.         sorted correctly
  23.  :History.    1.08 added STDERR option; replaced all "ø"'s with "av."
  24.  :History.    1.09 added "LOCAL" and "IGNORE" to XStat.data
  25.  :History.    1.10 added multiple names feature to XStat.data N-lines
  26.  :History.         fixed a small bug in the NI/NO feature
  27.  :History.    1.11 multiple XStat.data N-lines implemented
  28.  :History.         XStat.data N-line entry ".default." implemented
  29.  :History.         Xferstat "EOF" bug fixed
  30.  :History.         string length bumped to 256
  31.  :History.    1.12 added ".illegal." for missing host names in XFerStat
  32.  :History.         XStat now skips the 1st 10 chars of the 2nd XferStat line
  33.  :Contents.   Extracts statistics from UUCiCo "Xferstat" logfile. Requires
  34.  :Contents.   one of the "Xferstat" UUCICOs (tested with the SWB UUCiCo)
  35.  **************************************************************************)
  36.  
  37.  
  38.  
  39. MODULE XStat;
  40. (*$ StackChk:=FALSE *)
  41. (*$ CaseChk:=FALSE *)
  42. (*$ Volatile:=FALSE *)
  43.  
  44.  
  45.  
  46. (*$ DEFINE BETA:=FALSE *)
  47.  
  48.  
  49.  
  50. IMPORT Arts;
  51. IMPORT ASCII;
  52. IMPORT Break;
  53. IMPORT DosD;
  54. IMPORT DosL;
  55. IMPORT ExecD;
  56. IMPORT ExecL;
  57. IMPORT InOut;
  58. IMPORT R;
  59. IMPORT RealConversions;
  60. IMPORT RealInOut;
  61. IMPORT String;
  62.  
  63. IMPORT SYSTEM;
  64.  
  65.  
  66.  
  67. CONST
  68.  strlen=255;        (* was 100 before 1.11 *)
  69.  
  70.  smult=1;           (* seconds per second :-) *)
  71.  mmult=60*smult;    (* seconds per minute *)
  72.  hmult=60*mmult;    (* seconds per hour *)
  73.  dmult=24*hmult;    (* seconds per day *)
  74.  
  75.  dsun=0*dmult;      (* sunday offset into week (in seconds) *)
  76.  dmon=1*dmult;      (* monday offset into week (in seconds) *)
  77.  dtue=2*dmult;      (* tuesday offset into week (in seconds) *)
  78.  dwed=3*dmult;      (* wednesday offset into week (in seconds) *)
  79.  dthu=4*dmult;      (* thursday offset into week (in seconds) *)
  80.  dfri=5*dmult;      (* friday offset into week (in seconds) *)
  81.  dsat=6*dmult;      (* saturday offset into week (in seconds) *)
  82.  
  83.  defxstatdata="UULIB:XStat.data";
  84.  defxferstat="UUSPOOL:XferStat";
  85.  
  86.  vers="XStat 1.12" (*$ IF BETA *) +" *BETA*" (*$ ENDIF *) ;
  87.  version="$VER: "+vers+" (23-APR-93)";
  88.  argTemplate="FILE/K,DATA/K,V=VERBOSE/S,M=MONTH/K,NI=NOINCOM/S,NO=NOOUTGO/S,"+
  89.              "Q=QUIET/S,H=HOSTNAME/K,FD=FROMDATE/K,TD=TODATE/K,SE=STDERR/S";
  90.  
  91.  extendedhelp="\nXStat "+argTemplate+"\n"+
  92.              "\n DATA      XStat.data file\t\t  def="+defxstatdata+
  93.              "\n FILE      Xferstat file\t\t  def="+defxferstat+
  94.              "\n VERBOSE   verbose mode switch  \t  def=off"+
  95.              "\n MONTH     monthly, override -f/-t\t  def=off (format MMM-YY)"+
  96.              "\n NOINCOM   don't evaluate incoming calls  def=off"+
  97.              "\n NOOUTGO   don't evaluate outgoing calls  def=off"+
  98.              "\n QUIET     suppress non-fatal errors\t  def=off"+
  99.              "\n HOSTNAME  evaluate calls to host\t  def=all"+
  100.              "\n FROMDATE  ignore calls before date\t  def=01-JAN-78"+
  101.              "\n TODATE    ignore calls after date\t  def=current date/time"+
  102.              "\n STDERR    send error messages to StdErr  def=off\n\n";
  103.  
  104.  
  105.  
  106. TYPE
  107.  StrPtr=POINTER TO ARRAY[0..255] OF CHAR; (* just a dummy declaration *)
  108.  
  109.  DosArgs=
  110.    RECORD
  111.      file: StrPtr;
  112.      data: StrPtr;
  113.      verb: LONGINT;
  114.      month: StrPtr;
  115.      noin: LONGINT;
  116.      noout: LONGINT;
  117.      quiet: LONGINT;
  118.      host: StrPtr;
  119.      fdate: StrPtr;
  120.      tdate: StrPtr;
  121.      stderr: LONGINT;
  122.    END;
  123.  
  124.  StringT=ARRAY[0..strlen] OF CHAR;
  125.  StringTPtr=POINTER TO StringT;
  126.  
  127.  
  128.  
  129.  File=
  130.    RECORD
  131.      fh: DosD.FileHandlePtr;
  132.      eof: BOOLEAN;
  133.    END;
  134.  
  135.  
  136.  
  137.  CostPtr=POINTER TO Cost;
  138.  Cost=
  139.    RECORD
  140.      next: CostPtr;
  141.      start: LONGINT;        (* start time for mpu and unit                  *)
  142.      stop: LONGINT;         (* end time for mpu and unit                    *)
  143.      mpu: REAL;             (* price per unit                               *)
  144.      unit: REAL;            (* unit duration                                *)
  145.    END;
  146.  
  147.  
  148.  
  149.  XSDataPtr=POINTER TO XSData;
  150.  XSData=
  151.    RECORD
  152.      next: XSDataPtr;
  153.      host: StringT;       (* host name                                    *)
  154.      local: BOOLEAN;      (* count transmissions, no cost, no units       *)
  155.      ignore: BOOLEAN;     (* ignore this host entirely                    *)
  156.      double: BOOLEAN;     (* if true, don't free the attached costlist    *)
  157.      costlist: CostPtr;   (* list of Cost records                         *)
  158.    END;
  159.  
  160.  
  161.  
  162.  Connect=
  163.    RECORD
  164.      host: StringT;                 (* host name *)
  165.      callout: BOOLEAN;              (* TRUE: outgoing call *)
  166.      start: LONGINT;                (* session start time *)
  167.      stop: LONGINT;                 (* session end time *)
  168.      sdate: DosD.Date;              (* session start time in dos format *)
  169.      inbb: LONGINT;                 (* gross bytes in *)
  170.      outbb: LONGINT;                (* gross bytes out *)
  171.      inbn: LONGINT;                 (* net bytes in *)
  172.      outbn: LONGINT;                (* net bytes out *)
  173.      cost: REAL;                    (* connection phone costs *)
  174.      units: LONGINT;                (* phone units consumed *)
  175.      seconds: LONGINT;              (* online time in seconds *)
  176.      local: BOOLEAN;                (* was local -> no cost, no units *)
  177.      ignore: BOOLEAN;               (* do not count this at all *)
  178.    END;
  179.  
  180.  
  181.  
  182.   StatisticsPtr=POINTER TO Statistics;
  183.   Statistics=
  184.     RECORD
  185.       next: StatisticsPtr;
  186.       name: StringT;
  187.       in: BOOLEAN;
  188.       connects: LONGINT;
  189.       inbb: LONGINT;
  190.       outbb: LONGINT;
  191.       inbn: LONGINT;
  192.       outbn: LONGINT;
  193.       cost: REAL;
  194.       units: LONGINT;
  195.       seconds: LONGINT;
  196.       bpeak: LONGINT;
  197.       npeak: LONGINT;
  198.       localconnects: LONGINT;
  199.       ignoreconnects: LONGINT;
  200.     END;
  201.  
  202.  
  203.  
  204.   Args=
  205.     RECORD
  206.       xstatdata: StringT;           (* xstat.data name and path;          *)
  207.       xferstat: StringT;            (* xferstat name and path;            *)
  208.       verbose: BOOLEAN;             (* lotsa output;              ; off   *)
  209.       from: DosD.Date;              (* from date;                 ; zero  *)
  210.       to: DosD.Date;                (* to date;                   ; TODAY *)
  211.       host: ARRAY[0..400] OF CHAR;  (* host name pattern;         ; all   *)
  212.       in: BOOLEAN;                  (* process incoming calls;    ; on    *)
  213.       out: BOOLEAN;                 (* process outgoing calls;    ; on    *)
  214.       quiet: BOOLEAN;               (* suppress non-fatal errors; ; off   *)
  215.       monthly: BOOLEAN;             (* monthly mode;              ; off   *)
  216.       perhost: BOOLEAN;             (* perhost mode;              ; off   *)
  217.       stderr: BOOLEAN;              (* stderr mode;               ; off   *)
  218.    END;
  219.  
  220.  
  221.  
  222. VAR
  223.   inf: File;
  224.   ln1,ln2,temp: StringT;
  225.   log: Connect;
  226.   totali,totalo: Statistics;
  227.   xsroot: XSDataPtr;
  228.   args: Args;
  229.   currency: StringT;
  230.   rdargsPtr: DosD.RDArgsPtr;
  231.   statroot: StatisticsPtr;
  232.   index: INTEGER;
  233.   stdErr: DosD.FileHandlePtr;
  234.  
  235.  
  236.  
  237. (*---------------------------------------------------------------------------
  238.   FreeCostChain: FreeMem() a linked list of "Cost" records
  239.   ---------------------------------------------------------------------------*)
  240. PROCEDURE FreeCostChain(d:CostPtr);
  241.   VAR
  242.     n: CostPtr;
  243.   BEGIN
  244.     REPEAT
  245.       n:=d^.next;
  246.       ExecL.FreeMem(d,SIZE(d^));
  247.       d:=n;
  248.     UNTIL n=NIL;
  249.   END FreeCostChain;
  250.  
  251.  
  252.  
  253. (*---------------------------------------------------------------------------
  254.   FreeStat: FreeMem() a linked list of "Statistics" records
  255.   ---------------------------------------------------------------------------*)
  256. PROCEDURE FreeStat(s: StatisticsPtr);
  257.   VAR
  258.     n: StatisticsPtr;
  259.   BEGIN
  260.     (* some memory may be lost if an error occurs while the XStat.data file  *)
  261.     (* is being parsed... that's because the n^.tmp field is not checked     *)
  262.     (* here. the problem is, checking n^.tmp might cause a "memory freed     *)
  263.     (* twice" guru under certain conditions. losing a few bytes seems like   *)
  264.     (* the smaller evil in this case.                                        *)
  265.     REPEAT
  266.       n:=s^.next;
  267.       ExecL.FreeMem(s,SIZE(s^));
  268.       s:=n;
  269.     UNTIL n=NIL;
  270.   END FreeStat;
  271.  
  272.  
  273.  
  274. (*---------------------------------------------------------------------------
  275.   FreeXData: FreeMem() a linked list of "XSData" records, freeing the
  276.              attached "Cost" record lists as well (if double=FALSE)
  277.   ---------------------------------------------------------------------------*)
  278. PROCEDURE FreeXData(d: XSDataPtr);
  279.   VAR
  280.     n: XSDataPtr;
  281.   BEGIN
  282.     REPEAT
  283.       n:=d^.next;
  284.       IF (d^.costlist#NIL) AND (NOT d^.double) THEN
  285.         FreeCostChain(d^.costlist);
  286.       END;
  287.       ExecL.FreeMem(d,SIZE(d^));
  288.       d:=n;
  289.     UNTIL n=NIL;
  290.   END FreeXData;
  291.  
  292.  
  293.  
  294. (*---------------------------------------------------------------------------
  295.   MyError: Display error messages, terminate program if necessary
  296.            redirect errors to STDERR if requested by new 1.08 cli switch
  297.   ---------------------------------------------------------------------------*)
  298. (*$ CopyDyn:=FALSE *)
  299. PROCEDURE MyError(x1,x2,x3: ARRAY OF CHAR; fatal: BOOLEAN);
  300.   VAR
  301.     lidummy: LONGINT;
  302.   BEGIN
  303.     IF stdErr#NIL THEN
  304.       IF fatal THEN
  305.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("FATAL ERROR "),12);
  306.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x1),String.Length(x1));
  307.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  308.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x2),String.Length(x2));
  309.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  310.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x3),String.Length(x3));
  311.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\n"),2);
  312.         Arts.Exit(DosD.fail);
  313.       ELSIF (NOT args.quiet) OR ((x1[0]="F") & (x1[1]="A") & (x1[2]="T")) THEN
  314.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x1),String.Length(x1));
  315.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  316.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x2),String.Length(x2));
  317.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  318.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x3),String.Length(x3));
  319.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\n"),2);
  320.       END;
  321.     ELSE
  322.       IF fatal THEN
  323.         InOut.WriteString("FATAL ERROR ");
  324.         InOut.WriteString(x1); InOut.WriteLn;
  325.         InOut.WriteString(x2); InOut.WriteLn;
  326.         InOut.WriteString(x3); InOut.WriteLn;
  327.         InOut.WriteLn;
  328.         Arts.Exit(DosD.fail);
  329.       ELSIF (NOT args.quiet) OR ((x1[0]="F") & (x1[1]="A") & (x1[2]="T")) THEN
  330.         InOut.WriteString(x1); InOut.WriteLn;
  331.         InOut.WriteString(x2); InOut.WriteLn;
  332.         InOut.WriteString(x3); InOut.WriteLn;
  333.         InOut.WriteLn;
  334.       END;
  335.     END;
  336.   END MyError;
  337.  
  338.  
  339.  
  340. (*---------------------------------------------------------------------------
  341.   GetHead: Remove first char from a string, return it in char variable
  342.   ---------------------------------------------------------------------------*)
  343. PROCEDURE GetHead(VAR s: StringT; VAR c: CHAR);
  344.   BEGIN
  345.     c:=s[0];
  346.     String.DeleteChar(s,0);
  347.   END GetHead;
  348.  
  349.  
  350.  
  351. (*---------------------------------------------------------------------------
  352.   KillSpaces: Remove leading spaces from a string
  353.   ---------------------------------------------------------------------------*)
  354. PROCEDURE KillSpaces(VAR l1: StringT);
  355.   VAR
  356.     c: CHAR;
  357.   BEGIN
  358.     WHILE l1[0]=" " DO
  359.       GetHead(l1,c);
  360.       Break.TestBreak();
  361.     END;
  362.   END KillSpaces;
  363.  
  364.  
  365.  
  366. (*---------------------------------------------------------------------------
  367.   Get: Remove first "word" from a string. "Word" means "a sequence of
  368.        non-spaces, terminated by at least one space". Return this word
  369.        as a separate string
  370.   ---------------------------------------------------------------------------*)
  371. PROCEDURE Get(VAR s1,s2: StringT): BOOLEAN;
  372.   VAR
  373.     c: CHAR;
  374.   BEGIN
  375.     s2[0]:=ASCII.nul;
  376.     KillSpaces(s1);
  377.     GetHead(s1,c);
  378.     WHILE (c#" ") AND (c#ASCII.nul) DO
  379.       Break.TestBreak();
  380.       String.ConcatChar(s2,c);
  381.       GetHead(s1,c);
  382.     END;
  383.     RETURN String.Length(s2)>0;
  384.   END Get;
  385.  
  386.  
  387.  
  388. (*---------------------------------------------------------------------------
  389.   ConvDate: Convert ASCII representations of date and time to Dos format
  390.   ---------------------------------------------------------------------------*)
  391. (*$ CopyDyn:=FALSE *)
  392. PROCEDURE ConvDate(ds,ts: ARRAY OF CHAR; VAR dat: DosD.Date): BOOLEAN;
  393.   VAR
  394.     dt: DosD.DateTime;
  395.     res: LONGINT;
  396.   BEGIN
  397.     dt.format:=DosD.formatDOS;
  398.     dt.flags:=DosD.DateTimeFlagSet{};
  399.     dt.strDate:=SYSTEM.ADR(ds);
  400.     dt.strTime:=SYSTEM.ADR(ts);
  401.     res:=DosL.StrToDate(SYSTEM.ADR(dt));
  402.     dat:=dt.date;
  403.     RETURN res=0;
  404.   END ConvDate;
  405.  
  406.  
  407.  
  408. (*---------------------------------------------------------------------------
  409.   ParseDosArgs: Parse argument string and set "args" record accordingly
  410.   ---------------------------------------------------------------------------*)
  411. PROCEDURE ParseDosArgs(VAR s: StringT);
  412.   VAR
  413.     ts: StringT;
  414.     dargs: DosArgs;
  415.     rc: SYSTEM.ADDRESS;
  416.     pres: LONGINT;
  417.   BEGIN
  418.     rdargsPtr:=DosL.AllocDosObject(DosD.dosRdArgs,NIL);
  419.     IF rdargsPtr=NIL THEN
  420.       MyError("in command line parser",
  421.               "not enough memory",
  422.               "can't parse command line arguments",TRUE);
  423.     END;
  424.  
  425.     rdargsPtr^.source.buffer:=SYSTEM.ADR(s);
  426.     rdargsPtr^.source.length:=String.Length(s);
  427.     rdargsPtr^.source.curChr:=0;
  428.     rdargsPtr^.daList:=0;
  429.     rdargsPtr^.buffer:=NIL;
  430.     rdargsPtr^.bufSiz:=0;
  431.     rdargsPtr^.extHelp:=SYSTEM.ADR(extendedhelp);
  432.     rdargsPtr^.flags:=DosD.RDAFlagSet{};
  433.  
  434.     IF DosL.ReadArgs(SYSTEM.ADR(argTemplate),SYSTEM.ADR(dargs),rdargsPtr)=NIL THEN
  435.       MyError("in command line parser","illegal command line",s,TRUE);
  436.     END;
  437.  
  438.     IF dargs.file#NIL THEN
  439.       String.Copy(args.xferstat,dargs.file^);
  440.     END;
  441.  
  442.     IF dargs.data#NIL THEN
  443.       String.Copy(args.xstatdata,dargs.data^);
  444.     END;
  445.  
  446.     args.verbose:=(dargs.verb#0);
  447.  
  448.     IF dargs.fdate#NIL THEN
  449.       IF ConvDate(dargs.fdate^,"00:00:00",args.from) THEN
  450.         MyError("in command line","illegal FDATE",dargs.fdate^,TRUE);
  451.       END;
  452.     END;
  453.  
  454.     IF dargs.tdate#NIL THEN
  455.       IF ConvDate(dargs.tdate^,"23:59:59",args.to) THEN
  456.         MyError("in command line","illegal TDATE",dargs.tdate^,TRUE);
  457.       END;
  458.     END;
  459.  
  460.     IF dargs.host#NIL THEN
  461.       pres:=DosL.ParsePatternNoCase(dargs.host,
  462.                                     SYSTEM.ADR(args.host),
  463.                                     SIZE(args.host)-2); (* just being cautious :-) *)
  464.       IF pres<0 THEN
  465.         MyError("in command line","pattern in HOST too complex",dargs.host^,TRUE);
  466.       END;
  467.       args.perhost:=(pres=1);
  468.     END;
  469.  
  470.     IF args.in THEN
  471.       args.in:=(dargs.noin=0);
  472.     END;
  473.  
  474.     IF args.out THEN
  475.       args.out:=(dargs.noout=0);
  476.     END;
  477.  
  478.     args.quiet:=args.quiet OR (dargs.quiet#0);
  479.  
  480.     args.stderr:=(dargs.stderr#0);
  481.  
  482.     IF dargs.month#NIL THEN
  483.       ts:="01-";
  484.       String.Concat(ts,dargs.month^);
  485.       IF ConvDate(ts,"00:00:00",args.from) THEN
  486.         MyError("in command line","illegal MONTH",dargs.month^,TRUE);
  487.       END;
  488.       ts:="31-";
  489.       String.Concat(ts,dargs.month^);
  490.       IF ConvDate(ts,"23:59:59",args.to) THEN
  491.         ts:="30-";
  492.         String.Concat(ts,dargs.month^);
  493.         IF ConvDate(ts,"23:59:59",args.to) THEN
  494.           ts:="29-";
  495.           String.Concat(ts,dargs.month^);
  496.           IF ConvDate(ts,"23:59:59",args.to) THEN
  497.             ts:="28-";
  498.             String.Concat(ts,dargs.month^);
  499.             IF ConvDate(ts,"23:59:59",args.to) THEN
  500.               MyError("in command line","illegal MONTH",dargs.month^,TRUE);
  501.             END;
  502.           END;
  503.         END;
  504.       END;
  505.       args.monthly:=TRUE;
  506.     END;
  507.     IF rdargsPtr#NIL THEN
  508.       DosL.FreeArgs(rdargsPtr);
  509.       DosL.FreeDosObject(DosD.dosRdArgs,rdargsPtr);
  510.       rdargsPtr:=NIL;
  511.     END;
  512.   END ParseDosArgs;
  513.  
  514.  
  515.  
  516. (*---------------------------------------------------------------------------
  517.   PresetArgs: Set default values for "args" record
  518.   ---------------------------------------------------------------------------*)
  519. PROCEDURE PresetArgs();
  520.   BEGIN
  521.     args.xstatdata:=defxstatdata;
  522.     args.xferstat:=defxferstat;
  523.     args.verbose:=FALSE;
  524.     args.from.days:=0;   (* "01-JAN-78" "00:00:00" *)
  525.     args.from.minute:=0; (* "01-JAN-78" "00:00:00" *)
  526.     args.from.tick:=0;   (* "01-JAN-78" "00:00:00" *)
  527.     DosL.DateStamp(SYSTEM.ADR(args.to));
  528.     args.host[0]:=ASCII.nul;
  529.     args.in:=TRUE;
  530.     args.out:=TRUE;
  531.     args.quiet:=FALSE;
  532.     args.monthly:=FALSE;
  533.   END PresetArgs;
  534.  
  535.  
  536.  
  537. (*---------------------------------------------------------------------------
  538.   GetLine: Read a line from the input file
  539.   ---------------------------------------------------------------------------*)
  540. PROCEDURE GetLine(VAR line: StringT);
  541.   VAR
  542.     res: LONGINT;
  543.   BEGIN
  544.     IF inf.fh#NIL THEN
  545.       res:=DosL.FGets(inf.fh,SYSTEM.ADR(line),strlen);
  546.       inf.eof:=(res=0) AND (DosL.IoErr()=0);
  547.     END;
  548.     IF (String.Length(line)>0) AND (line[String.Length(line)-1]=ASCII.lf) THEN
  549.       line[String.Length(line)-1]:="\o";
  550.     END;
  551.   END GetLine;
  552.  
  553.  
  554.  
  555. (*---------------------------------------------------------------------------
  556.   GetLog: Read a two-line log entry from Xferstat
  557.   ---------------------------------------------------------------------------*)
  558. PROCEDURE GetLog(VAR l1,l2: StringT);
  559.   BEGIN
  560.     l1[0]:="\o";
  561.     l2[0]:="\o";
  562.     REPEAT
  563.       Break.TestBreak();
  564.       GetLine(l1);
  565.     UNTIL (inf.eof) OR (l1[0]="<") OR (l1[0]=">");
  566.     IF NOT inf.eof THEN
  567.       GetLine(l2);
  568.     ELSE
  569.       l1[0]:="\o";
  570.     END;
  571.   END GetLog;
  572.  
  573.  
  574.  
  575. (*---------------------------------------------------------------------------
  576.   Skip: Skip the first "word" of a string; refer to "Get()" for an
  577.         explanation ot the term "word"
  578.   ---------------------------------------------------------------------------*)
  579. PROCEDURE Skip(VAR s: StringT);
  580.   VAR
  581.     dum: BOOLEAN;
  582.     x: StringT;
  583.   BEGIN
  584.     dum:=Get(s,x);
  585.   END Skip;
  586.  
  587.  
  588.  
  589. (*---------------------------------------------------------------------------
  590.   GetLNum: Get the first "word" of a string, and convert it to a LONGINT
  591.            (if possible). Refer to "Get()" for an explanation of the
  592.            term "word"
  593.   ---------------------------------------------------------------------------*)
  594. PROCEDURE GetLNum(VAR s: StringT; VAR x: LONGINT): BOOLEAN;
  595.   VAR
  596.     sn: StringT;
  597.     res: LONGINT;
  598.   BEGIN
  599.     IF Get(s,sn) THEN
  600.       res:=DosL.StrToLong(SYSTEM.ADR(sn),x);
  601.       RETURN res>0;
  602.     END;
  603.     RETURN FALSE;
  604.   END GetLNum;
  605.  
  606.  
  607.  
  608. (*---------------------------------------------------------------------------
  609.   GetNum: Call "GetLNum()" and make sure the result is an INTEGER
  610.   ---------------------------------------------------------------------------*)
  611. PROCEDURE GetNum(VAR s: StringT; VAR x: INTEGER): BOOLEAN;
  612.   VAR
  613.     l: LONGINT;
  614.   BEGIN
  615.     IF GetLNum(s,l) THEN
  616.       IF l>MAX(INTEGER) THEN
  617.         x:=0;
  618.       ELSE
  619.         x:=INTEGER(l);
  620.         RETURN TRUE;
  621.       END;
  622.     ELSE
  623.       x:=0;
  624.     END;
  625.     RETURN FALSE;
  626.   END GetNum;
  627.  
  628.  
  629.  
  630. (*---------------------------------------------------------------------------
  631.   GetReal: Get the first "word" of a string, and convert it to a REAL
  632.            (if possible). Refer to "Get()" for an explanation of the
  633.            term "word"
  634.   ---------------------------------------------------------------------------*)
  635. PROCEDURE GetReal(VAR s: StringT; VAR x: REAL): BOOLEAN;
  636.   VAR
  637.     sn: StringT;
  638.     err: BOOLEAN;
  639.   BEGIN
  640.     IF Get(s,sn) THEN
  641.       RealConversions.StrToReal(sn,x,err);
  642.       RETURN NOT err;
  643.     END;
  644.     RETURN FALSE;
  645.   END GetReal;
  646.  
  647.  
  648.  
  649. (*---------------------------------------------------------------------------
  650.   GetTime: Get the first two "words" of a string, and convert them to
  651.            Dos format date and time (if possible). Refer to "Get()" for an
  652.            explanation of the term "word"
  653.   ---------------------------------------------------------------------------*)
  654. PROCEDURE GetTime(VAR s: StringT; VAR t: LONGINT; VAR dos: DosD.Date): BOOLEAN;
  655.   VAR
  656.     ds,ts: StringT;
  657.     dt: DosD.DateTime;
  658.   BEGIN
  659.     IF Get(s,ds) THEN
  660.       IF Get(s,ts) THEN
  661.         dt.format:=DosD.formatCDN;
  662.         dt.flags:=DosD.DateTimeFlagSet{};
  663.         dt.strDate:=SYSTEM.ADR(ds);
  664.         dt.strTime:=SYSTEM.ADR(ts);
  665.  
  666.         IF DosL.StrToDate(SYSTEM.ADR(dt))#0 THEN
  667.           dos:=dt.date;
  668.           t:=(dos.days MOD 7)*dmult+(dos.minute*mmult)+(dos.tick DIV 50);
  669.           RETURN TRUE;
  670.         END;
  671.       END;
  672.     END;
  673.     RETURN FALSE;
  674.   END GetTime;
  675.  
  676.  
  677.  
  678. (*---------------------------------------------------------------------------
  679.   TestXStatHeader: Check if the input file's header is "H XSTAT DATA"
  680.   ---------------------------------------------------------------------------*)
  681. PROCEDURE TestXStatHeader();
  682.  VAR
  683.   s1,s,t: StringT;
  684.   c: CHAR;
  685.  BEGIN
  686.   GetLine(s);
  687.   s1:=s;
  688.   GetHead(s,c);
  689.   IF c="H" THEN
  690.    IF Get(s,t) OR (String.Compare(t,"XSTAT")#0) THEN
  691.     IF NOT (Get(s,t) OR (String.Compare(t,"DATA")#0)) THEN
  692.      MyError("in XStat.data header",
  693.              s1,"keyword DATA not found",TRUE);
  694.     END;
  695.    ELSE
  696.     MyError("in XStat.data header",
  697.             s1,"keyword XSTAT not found",TRUE);
  698.    END;
  699.   ELSE
  700.    MyError("in XStat.data header",
  701.            s1,"header identifier H not found",TRUE);
  702.   END;
  703.  END TestXStatHeader;
  704.  
  705.  
  706.  
  707. (*---------------------------------------------------------------------------
  708.   ParseXSDataNEntry: Parse a "host name" line from XStat.data
  709.   ---------------------------------------------------------------------------*)
  710. PROCEDURE ParseXSDataNEntry(VAR s,s1: StringT; VAR ccxsd: XSDataPtr);
  711.   VAR
  712.     cxsd: XSDataPtr;
  713.     xsd: XSData;
  714.     t: StringT;
  715.     first: BOOLEAN;
  716.   BEGIN
  717.     IF (ccxsd#NIL) AND (ccxsd^.costlist=NIL) AND (NOT ccxsd^.local) AND (NOT ccxsd^.ignore) THEN
  718. (* -------- obsolete --------
  719.       MyError("in XStat.data",
  720.               "N-line without subsequent C-lines for",
  721.               ccxsd^.host,TRUE);
  722.     END;
  723.    -------------------------- *)
  724.       first:=FALSE;
  725.     ELSE
  726.       first:=TRUE;
  727.     END;
  728.  
  729.     IF Get(s,t) THEN
  730.       REPEAT
  731.         xsd.next:=NIL;
  732.         xsd.host:=t;
  733.         xsd.costlist:=NIL;
  734.         xsd.local:=FALSE;
  735.         xsd.ignore:=FALSE;
  736.         xsd.double:=FALSE;
  737.         cxsd:=xsroot;
  738.         IF xsroot#NIL THEN
  739.           IF String.Compare(xsd.host,cxsd^.host)=0 THEN
  740.             MyError("in XStat.data",
  741.                     "host name already used for",
  742.                     xsd.host,TRUE);
  743.           END;
  744.           WHILE cxsd^.next#NIL DO
  745.             IF String.Compare(xsd.host,cxsd^.next^.host)=0 THEN
  746.               MyError("in XStat.data",
  747.                       "host name already used for",
  748.                       xsd.host,TRUE);
  749.             END;
  750.             Break.TestBreak();
  751.             cxsd:=cxsd^.next;
  752.           END;
  753.           cxsd^.next:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
  754.           IF cxsd^.next=NIL THEN
  755.             MyError("","not enough memory","",TRUE);
  756.           END;
  757.           cxsd:=cxsd^.next;
  758.           IF first THEN
  759.             first:=FALSE;
  760.             ccxsd:=cxsd;
  761.           ELSE
  762.             xsd.double:=TRUE;
  763.           END;
  764.         ELSE
  765.           xsroot:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
  766.           IF xsroot=NIL THEN
  767.             MyError("","not enough memory","",TRUE);
  768.           END;
  769.           cxsd:=xsroot;
  770.           ccxsd:=cxsd;
  771.           first:=FALSE;
  772.         END;
  773.         cxsd^:=xsd;
  774.       UNTIL NOT Get(s,t);
  775.     ELSE
  776.       MyError("in XStat.data",
  777.               s1,"no host name found in N-line",TRUE);
  778.     END;
  779.   END ParseXSDataNEntry;
  780.  
  781.  
  782.  
  783. (*---------------------------------------------------------------------------
  784.   ParseXSDataCEntry: Parse a "cost info" line from XStat.data
  785.   ---------------------------------------------------------------------------*)
  786. PROCEDURE ParseXSDataCEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  787.   VAR
  788.     t,t1: StringT;
  789.     cost: Cost;
  790.     ccp: CostPtr;
  791.     temp: INTEGER;
  792.     c0{R.D7},c1{R.D6}: CHAR;
  793.   BEGIN
  794.     IF cxsd=NIL THEN
  795.       MyError("in XStat.data",s1,"C-line without corresponding N-line",TRUE);
  796.     END;
  797.  
  798.     cost.next:=NIL;
  799.     cost.stop:=7*dmult-1;
  800.  
  801.     IF NOT Get(s,t) THEN
  802.       MyError("in XStat.data",s1,"illegal or missing start time in C-line",TRUE);
  803.     END;
  804.  
  805.     IF String.Compare(t,"LOCAL")=0 THEN
  806.       IF cxsd^.costlist#NIL THEN
  807.         MyError("in XStat.data",s1,"LOCAL must be the only C-line for the host",TRUE);
  808.       ELSIF cxsd^.local#FALSE THEN
  809.         MyError("in XStat.data",s1,"duplicate LOCAL after N-line",TRUE);
  810.       ELSIF cxsd^.ignore#FALSE THEN
  811.         MyError("in XStat.data",s1,"LOCAL and IGNORE are mutually exclusive",TRUE);
  812.       ELSE
  813.         cxsd^.local:=TRUE;
  814.       END;
  815.  
  816.     ELSIF String.Compare(t,"IGNORE")=0 THEN
  817.       IF cxsd^.costlist#NIL THEN
  818.         MyError("in XStat.data",s1,"IGNORE must be the only C-line for the host",TRUE);
  819.       ELSIF cxsd^.ignore#FALSE THEN
  820.         MyError("in XStat.data",s1,"duplicate IGNORE after N-line",TRUE);
  821.       ELSIF cxsd^.local#FALSE THEN
  822.         MyError("in XStat.data",s1,"LOCAL and IGNORE are mutually exclusive",TRUE);
  823.       ELSE
  824.         cxsd^.ignore:=TRUE;
  825.       END;
  826.  
  827.     ELSIF (t[2]="-") AND (t[5]=":") AND (t[8]=":") AND (String.Length(t)=11) THEN
  828.       c0:=t[0]; c1:=t[1];
  829.       IF (c0="S") AND (c1="U") THEN
  830.         cost.start:=dsun;
  831.       ELSIF (c0="M") AND (c1="O") THEN
  832.         cost.start:=dmon;
  833.       ELSIF (c0="T") AND (c1="U") THEN
  834.         cost.start:=dtue;
  835.       ELSIF (c0="W") AND (c1="E") THEN
  836.         cost.start:=dwed;
  837.       ELSIF (c0="T") AND (c1="H") THEN
  838.         cost.start:=dthu;
  839.       ELSIF (c0="F") AND (c1="R") THEN
  840.         cost.start:=dfri;
  841.       ELSIF (c0="S") AND (c1="A") THEN
  842.         cost.start:=dsat;
  843.       ELSE
  844.         MyError("in XStat.data",s1,"illegal day of week in start time in C-line",TRUE);
  845.       END;
  846.  
  847.       t[0]:=" "; t[1]:=" "; t[2]:=" "; t[5]:=" "; t[8]:=" ";
  848.  
  849.       IF NOT GetNum(t,temp) THEN
  850.         MyError("in XStat.data",s1,"illegal 'hours' part in start time in C-line",TRUE);
  851.       END;
  852.       cost.start:=cost.start+hmult*LONGINT(temp);
  853.  
  854.       IF NOT GetNum(t,temp) THEN
  855.         MyError("in XStat.data",s1,"illegal 'minutes' part in start time in C-line",TRUE);
  856.       END;
  857.       cost.start:=cost.start+mmult*LONGINT(temp);
  858.  
  859.       IF NOT GetNum(t,temp) THEN
  860.         MyError("in XStat.data",s1,"illegal 'seconds' part in start time in C-line",TRUE);
  861.       END;
  862.       cost.start:=cost.start+smult*LONGINT(temp);
  863.  
  864.       IF NOT GetReal(s,cost.unit) THEN
  865.         MyError("in XStat.data",s1,"illegal 'seconds per unit' in C-line",TRUE);
  866.       END;
  867.  
  868.       IF NOT GetReal(s,cost.mpu) THEN
  869.         MyError("in XStat.data",s1,"illegal 'money per unit' in C-line",TRUE);
  870.       END;
  871.  
  872.       IF (cxsd^.local) OR (cxsd^.ignore) THEN
  873.         MyError("in XStat.data",s1,"no more C-lines allowed after LOCAL or IGNORE",TRUE);
  874.       END;
  875.  
  876.       ccp:=cxsd^.costlist;
  877.       IF ccp#NIL THEN
  878.         WHILE ccp^.next#NIL DO
  879.           Break.TestBreak();
  880.           ccp:=ccp^.next;
  881.         END;
  882.         ccp^.next:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
  883.         IF ccp^.next=NIL THEN
  884.           MyError("","not enough memory","",TRUE);
  885.         END;
  886.         ccp^.next^:=cost;
  887.         ccp^.stop:=cost.start-1;
  888.         IF ccp^.stop<ccp^.start THEN
  889.           MyError("in XStat.data","connect start time sequence error",
  890.                   "(you probably swapped some C-lines, or you left out an N-line)",TRUE);
  891.         END;
  892.       ELSE
  893.         cxsd^.costlist:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
  894.         IF cxsd^.costlist=NIL THEN
  895.           MyError("","not enough memory","",TRUE);
  896.         END;
  897.         cxsd^.costlist^:=cost;
  898.         IF cost.start#0 THEN
  899.           MyError("in XStat.data",s1,"start time in first C-line must be SU-00:00:00",TRUE);
  900.         END;
  901.       END;
  902.     ELSE
  903.       MyError("in XStat.data",s1,"illegal or missing start time in C-line",TRUE);
  904.     END;
  905.  
  906.     (* cxsd will always be #NIL here... *)
  907.     WHILE cxsd^.next#NIL DO
  908.       (* copy costlistPtr to next^.costlistPtr *)
  909.       cxsd^.next^.costlist:=cxsd^.costlist;
  910.       cxsd^.next^.local:=cxsd^.local;
  911.       cxsd^.next^.ignore:=cxsd^.ignore;
  912.       (* advance cxsd to cxsd#.next; note: cxsd is a VAR parameter, so this *)
  913.       (* will only happen for the first C-record found... from then on,     *)
  914.       (* this costlistPtr copying will not be done again.                   *)
  915.       cxsd:=cxsd^.next;
  916.     END;
  917.   END ParseXSDataCEntry;
  918.  
  919.  
  920.  
  921. (*---------------------------------------------------------------------------
  922.   ParseXSDataSEntry: Parse a "currency sign info" line from XStat.data
  923.   ---------------------------------------------------------------------------*)
  924. PROCEDURE ParseXSDataSEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  925.  VAR
  926.   t: StringT;
  927.  BEGIN
  928.   IF (cxsd#NIL) THEN
  929.    MyError("in XStat.data",
  930.            s1,"S-line after the first connection cost record",TRUE);
  931.   END;
  932.  
  933.   IF Get(s,t) THEN
  934.    IF currency[0]=ASCII.nul THEN
  935.     currency:=t;
  936.     IF currency[0]#" " THEN
  937.      String.Insert(currency,0," \o");
  938.     END;
  939.    ELSE
  940.     MyError("in XStat.data",
  941.             s1,"duplicate S-line found",TRUE);
  942.    END;
  943.   ELSE
  944.    MyError("in XStat.data",
  945.            s1,"missing currency sign in S-line",TRUE);
  946.   END;
  947.  END ParseXSDataSEntry;
  948.  
  949.  
  950.  
  951. (*---------------------------------------------------------------------------
  952.   GetXStatData: Read and parse the XStat.data file
  953.   ---------------------------------------------------------------------------*)
  954. PROCEDURE GetXStatData();
  955.  VAR
  956.   s1,s: StringT;
  957.   c: CHAR;
  958.   cxsd: XSDataPtr;
  959.  BEGIN
  960.   cxsd:=NIL;
  961.   TestXStatHeader();
  962.   REPEAT
  963.    Break.TestBreak();
  964.    GetLine(s);
  965.    s1:=s;
  966.    GetHead(s,c);
  967.    CASE c OF
  968.     |"#": (* NOP *)
  969.     |"N": ParseXSDataNEntry(s,s1,cxsd);
  970.     |"C": ParseXSDataCEntry(s,s1,cxsd);
  971.     |"S": ParseXSDataSEntry(s,s1,cxsd);
  972.     |ELSE (* unknown: NOP *)
  973.    END;
  974.   UNTIL inf.eof;
  975.  END GetXStatData;
  976.  
  977.  
  978.  
  979. (*---------------------------------------------------------------------------
  980.   ParseLog: Parse a two-line connection data entry from Xferstat
  981.   ---------------------------------------------------------------------------*)
  982. (*$ CopyDyn:=FALSE *)
  983. PROCEDURE ParseLog(line1,line2: StringT; VAR log: Connect): BOOLEAN;
  984.  VAR
  985.   c: CHAR;
  986.   l1,l2: StringT;
  987.   dummy: DosD.Date;
  988.   i: INTEGER;
  989.  BEGIN
  990.   l1:=line1;
  991.   l2:=line2;
  992.   GetHead(l1,c);
  993.   log.callout:=(c="<");
  994.  
  995.   IF Get(l1,log.host) THEN
  996. (* ---------------------------------------------------
  997.    IF (log.host[0]>="0") AND (log.host[0]<="9") THEN
  998.     MyError("invalid log entry: can't find host name",line1,line2,FALSE);
  999.     RETURN FALSE;
  1000.    END;
  1001.    --------------------------------------------------- *)
  1002.    IF line1[2]=" " THEN
  1003.      log.host:=".illegal.";
  1004.      MyError("invalid log entry: can't find host name (substituting .illegal.)",line1,line2,FALSE);
  1005.      l1:=line1;
  1006.      GetHead(l1,c);
  1007.    END;
  1008.    IF GetTime(l1,log.start,log.sdate) THEN
  1009.     Skip(l1);
  1010.     IF GetTime(l1,log.stop,dummy) THEN
  1011.      GetHead(l2,c);
  1012.      IF c="|" THEN
  1013.       FOR i:=1 TO 9 DO
  1014.         GetHead(l2,c); (* skip the initial "|" and the next 9 chars *)
  1015.       END;
  1016.       IF GetLNum(l2,log.inbb) THEN
  1017.        IF GetLNum(l2,log.outbb) THEN
  1018.         Skip(l2);
  1019.         IF GetLNum(l2,log.inbn) THEN
  1020.          IF GetLNum(l2,log.outbn) THEN
  1021.           RETURN TRUE;
  1022.          ELSE
  1023.           MyError("invalid log entry: can't get net send bytes",line1,line2,FALSE);
  1024.          END;
  1025.         ELSE
  1026.          MyError("invalid log entry: can't get net receive bytes",line1,line2,FALSE);
  1027.         END;
  1028.        ELSE
  1029.         MyError("invalid log entry: can't get gross send bytes",line1,line2,FALSE);
  1030.        END;
  1031.       ELSE
  1032.        MyError("invalid log entry: can't get gross receive bytes",line1,line2,FALSE);
  1033.       END;
  1034.      ELSE
  1035.       MyError("invalid log entry: illegal continuation line header ",line1,line2,FALSE);
  1036.      END;
  1037.     ELSE
  1038.      MyError("invalid log entry: can't compute end time",line1,line2,FALSE);
  1039.     END;
  1040.    ELSE
  1041.     MyError("invalid log entry: can't compute start time",line1,line2,FALSE);
  1042.    END;
  1043.   ELSE
  1044.    MyError("invalid log entry: can't find host name",line1,line2,FALSE);
  1045.   END;
  1046.   RETURN FALSE;
  1047.  END ParseLog;
  1048.  
  1049.  
  1050.  
  1051. (*---------------------------------------------------------------------------
  1052.   ComputeCost: Compute the cost for a single connect
  1053.   ---------------------------------------------------------------------------*)
  1054. PROCEDURE ComputeCost(VAR log: Connect): BOOLEAN;
  1055.  VAR
  1056.   cxsd: XSDataPtr;
  1057.   ccost: CostPtr;
  1058.   time: REAL;
  1059.  BEGIN
  1060.   log.seconds:=log.stop-log.start+1;
  1061.   IF log.seconds<0 THEN
  1062.     log.seconds:=log.seconds+7*dmult;
  1063.   END;
  1064.  
  1065.   cxsd:=xsroot;
  1066.   WHILE (cxsd#NIL) AND (String.Compare(cxsd^.host,log.host)#0) DO
  1067.     Break.TestBreak();
  1068.     cxsd:=cxsd^.next;
  1069.   END;
  1070.   IF cxsd=NIL THEN
  1071.     cxsd:=xsroot;
  1072.     WHILE (cxsd#NIL) AND (String.Compare(cxsd^.host,".default.")#0) DO
  1073.       Break.TestBreak();
  1074.       cxsd:=cxsd^.next;
  1075.     END;
  1076.     IF cxsd=NIL THEN
  1077.       MyError("ERROR: no connection cost data found for host",log.host,
  1078.               "extend your XStat.data file or use '.default.'!",FALSE);
  1079.       RETURN FALSE;
  1080.     END;
  1081.   END;
  1082.  
  1083.   IF NOT (cxsd^.local OR cxsd^.ignore) THEN
  1084.     ccost:=cxsd^.costlist;
  1085.     WHILE log.start>ccost^.stop DO
  1086.       Break.TestBreak();
  1087.       ccost:=ccost^.next;
  1088.     END;
  1089.   END;
  1090.  
  1091.   IF log.stop<log.start THEN
  1092.     log.stop:=log.stop+7*dmult;
  1093.   END;
  1094.   log.cost:=0.0;
  1095.   log.units:=0;
  1096.   IF NOT (cxsd^.local OR cxsd^.ignore) THEN
  1097.     time:=REAL(log.start);
  1098.     REPEAT
  1099.       Break.TestBreak();
  1100.       log.cost:=log.cost+ccost^.mpu;
  1101.       time:=time+ccost^.unit;
  1102.       INC(log.units);
  1103.  
  1104.       IF LONGINT(time)>ccost^.stop THEN
  1105.         ccost:=ccost^.next;
  1106.         IF ccost=NIL THEN
  1107.           ccost:=cxsd^.costlist;
  1108.           time:=time-REAL(7*dmult);
  1109.           log.stop:=log.stop-7*dmult;
  1110.         END;
  1111.       END;
  1112.     UNTIL LONGINT(time)>log.stop;
  1113.   END;
  1114.   log.local:=cxsd^.local;
  1115.   log.ignore:=cxsd^.ignore;
  1116.   RETURN TRUE;
  1117.  END ComputeCost;
  1118.  
  1119.  
  1120.  
  1121. (*---------------------------------------------------------------------------
  1122.   DisplayConnect: Display the statistics for a single connect
  1123.   ---------------------------------------------------------------------------*)
  1124. PROCEDURE DisplayConnect(log: Connect);
  1125.  VAR
  1126.   name: StringT;
  1127.  BEGIN
  1128.   name:=log.host;
  1129.   WHILE String.Length(name)<8 DO
  1130.    Break.TestBreak();
  1131.    String.Insert(name,0," \o");
  1132.   END;
  1133.   InOut.WriteString("\nhost name\t"); InOut.WriteString(name);
  1134.   InOut.WriteString("\ndirection\t     "); IF log.callout THEN
  1135.                                             InOut.WriteString("out");
  1136.                                            ELSE
  1137.                                             InOut.WriteString(" in");
  1138.                                            END;
  1139.   InOut.WriteString("\nonline time\t"); InOut.WriteInt(log.seconds,8); InOut.WriteString(" seconds");
  1140.   InOut.WriteString("\nunits\t\t");
  1141.   IF log.local THEN
  1142.     InOut.WriteString("LOCAL");
  1143.   ELSIF log.ignore THEN
  1144.     InOut.WriteString("IGNORE");
  1145.   ELSE
  1146.     InOut.WriteInt(log.units,8); InOut.WriteString(" units");
  1147.   END;
  1148.   InOut.WriteString("\ncost\t\t");
  1149.   IF log.local THEN
  1150.     InOut.WriteString("LOCAL");
  1151.   ELSIF log.ignore THEN
  1152.     InOut.WriteString("IGNORE");
  1153.   ELSE
  1154.     RealInOut.WriteReal(log.cost,8,2); InOut.Write(" "); InOut.WriteString(currency);
  1155.   END;
  1156.   InOut.WriteString("\n\n\n");
  1157.  END DisplayConnect;
  1158.  
  1159.  
  1160.  
  1161. (*---------------------------------------------------------------------------
  1162.   DisplayStats: Display the overall statistics
  1163.   ---------------------------------------------------------------------------*)
  1164. PROCEDURE DisplayStats(tot: Statistics);
  1165.  BEGIN
  1166.   InOut.WriteString("Connection statistics for ");
  1167.   IF NOT tot.in THEN
  1168.    InOut.WriteString("outgoing");
  1169.   ELSE
  1170.    InOut.WriteString("incoming");
  1171.   END;
  1172.   IF tot.name[0]="\o" THEN
  1173.     InOut.WriteString(" calls:\n");
  1174.     InOut.WriteString("-----------------------------------------\n");
  1175.   ELSE
  1176.     InOut.WriteString(" calls ");
  1177.     IF NOT tot.in THEN
  1178.       InOut.WriteString("to");
  1179.     ELSE
  1180.       InOut.WriteString("from");
  1181.     END;
  1182.     InOut.WriteString(' host "');
  1183.     InOut.WriteString(tot.name);
  1184.     InOut.WriteString('":\n');
  1185.   END;
  1186.  
  1187.   IF tot.connects=0 THEN
  1188.    InOut.WriteString("\nno connects recorded.\n\n\n");
  1189.   ELSE
  1190.    InOut.WriteString("\nconnects        "); InOut.WriteInt(tot.connects,8); InOut.WriteString("  \t  ("); InOut.WriteInt(tot.localconnects,0); InOut.WriteString(" of them were local)");
  1191.    IF tot.name[0]="\o" THEN
  1192.      InOut.WriteString("\nignored connects"); InOut.WriteInt(tot.ignoreconnects,8);
  1193.    END;
  1194.    InOut.WriteString("\nonline time     "); InOut.WriteInt(tot.seconds,8); InOut.WriteString(" sec\t  ("); InOut.WriteInt((tot.seconds+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" sec/connect)");
  1195.    InOut.WriteString("\nunits           "); InOut.WriteInt(tot.units,8); InOut.WriteString(" units\t  ("); RealInOut.WriteReal(REAL(tot.units)/REAL(tot.connects),8,3); InOut.WriteString(" units/connect)");
  1196.    InOut.WriteString("\ncost            "); RealInOut.WriteReal(tot.cost,8,2); InOut.WriteString(currency); InOut.WriteString("\t  ("); RealInOut.WriteReal(tot.cost/REAL(tot.connects),8,3); InOut.WriteString(currency); InOut.WriteString("/connect)");
  1197.  InOut.WriteString("\n\ngross read      "); InOut.WriteInt(tot.inbb,8); InOut.WriteString(" bytes\t  ("); InOut.WriteInt((tot.inbb+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
  1198.    InOut.WriteString("\ngross send      "); InOut.WriteInt(tot.outbb,8); InOut.WriteString(" bytes\t  ("); InOut.WriteInt((tot.outbb+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
  1199.    InOut.WriteString("\nnet read        "); InOut.WriteInt(tot.inbn,8); InOut.WriteString(" bytes\t  ("); InOut.WriteInt((tot.inbn+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
  1200.    InOut.WriteString("\nnet send        "); InOut.WriteInt(tot.outbn,8); InOut.WriteString(" bytes\t  (");  InOut.WriteInt((tot.outbn+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)\n");
  1201.    InOut.WriteString("\nav. gross speed "); InOut.WriteInt((tot.inbb+tot.outbb) DIV tot.seconds,8); InOut.WriteString(" cps\t  ("); InOut.WriteInt(tot.bpeak,8); InOut.WriteString(" cps peak)");
  1202.    InOut.WriteString("\nav. net speed   "); InOut.WriteInt((tot.inbn+tot.outbn) DIV tot.seconds,8); InOut.WriteString(" cps\t  ("); InOut.WriteInt(tot.npeak,8); InOut.WriteString(" cps peak)");
  1203.  
  1204.    InOut.WriteString("\nav. gross cost  ");
  1205.    IF tot.inbb+tot.outbb=0 THEN
  1206.     InOut.WriteString("       -");
  1207.    ELSE
  1208.     RealInOut.WriteReal(tot.cost/REAL(tot.inbb+tot.outbb)*1048576.0,8,3);
  1209.    END;
  1210.    InOut.WriteString(currency); InOut.WriteString("/MB");
  1211.  
  1212.    InOut.WriteString("\nav. net cost    ");
  1213.    IF tot.inbn+tot.outbn=0 THEN
  1214.     InOut.WriteString("       -");
  1215.    ELSE
  1216.     RealInOut.WriteReal(tot.cost/REAL(tot.inbn+tot.outbn)*1048576.0,8,3);
  1217.    END;
  1218.    InOut.WriteString(currency); InOut.WriteString("/MB\n\n\n");
  1219.   END;
  1220.  END DisplayStats;
  1221.  
  1222.  
  1223.  
  1224. (*---------------------------------------------------------------------------
  1225.   UpdateStat: Update the totali/totalo records
  1226.   ---------------------------------------------------------------------------*)
  1227. PROCEDURE UpdateStat(VAR tot: Statistics; VAR log: Connect);
  1228.  VAR
  1229.   tempspeed: LONGINT;
  1230.  BEGIN
  1231.   IF NOT log.ignore THEN
  1232.     INC(tot.connects);
  1233.     tot.seconds:=tot.seconds+log.seconds;
  1234.     tot.inbb:=tot.inbb+log.inbb;
  1235.     tot.outbb:=tot.outbb+log.outbb;
  1236.     tot.inbn:=tot.inbn+log.inbn;
  1237.     tot.outbn:=tot.outbn+log.outbn;
  1238.     tot.units:=tot.units+log.units;
  1239.     tot.cost:=tot.cost+log.cost;
  1240.  
  1241.     IF log.seconds#0 THEN
  1242.      tempspeed:=(log.inbb+log.outbb) DIV log.seconds;
  1243.      IF tempspeed>tot.bpeak THEN tot.bpeak:=tempspeed END;
  1244.  
  1245.      tempspeed:=(log.inbn+log.outbn) DIV log.seconds;
  1246.      IF tempspeed>tot.npeak THEN tot.npeak:=tempspeed END;
  1247.     END;
  1248.   END;
  1249.   IF log.ignore THEN
  1250.     INC(tot.ignoreconnects);
  1251.   END;
  1252.   IF log.local THEN
  1253.     INC(tot.localconnects);
  1254.   END;
  1255.  END UpdateStat;
  1256.  
  1257.  
  1258.  
  1259. (*---------------------------------------------------------------------------
  1260.   ClearStat: Preset a Statistics-record with zeros
  1261.   ---------------------------------------------------------------------------*)
  1262. PROCEDURE ClearStat(VAR stat: Statistics);
  1263.   BEGIN
  1264.     stat.next:=NIL;
  1265.     stat.name:="\o\o";
  1266.     stat.in:=FALSE;
  1267.     stat.connects:=0;
  1268.     stat.inbb:=0;
  1269.     stat.outbb:=0;
  1270.     stat.inbn:=0;
  1271.     stat.outbn:=0;
  1272.     stat.cost:=0.0;
  1273.     stat.units:=0;
  1274.     stat.seconds:=0;
  1275.     stat.bpeak:=0;
  1276.     stat.npeak:=0;
  1277.     stat.localconnects:=0;
  1278.     stat.ignoreconnects:=0;
  1279.   END ClearStat;
  1280.  
  1281.  
  1282.  
  1283. (*---------------------------------------------------------------------------
  1284.   InsertStat: Insert a Statistics-record at the proper position
  1285.               into the linked list
  1286.   ---------------------------------------------------------------------------*)
  1287. PROCEDURE InsertStat(stat: StatisticsPtr);
  1288.   VAR
  1289.     cur,prev: StatisticsPtr;
  1290.     done: BOOLEAN;
  1291.   BEGIN
  1292.     done:=FALSE;
  1293.     IF statroot=NIL THEN
  1294.       statroot:=stat;
  1295.     ELSE
  1296.       cur:=statroot;
  1297.       prev:=NIL;
  1298.       REPEAT
  1299.         IF String.Compare(stat^.name,cur^.name)=0 THEN
  1300.           IF stat^.in THEN
  1301.             (* insert before current *)
  1302.             IF prev=NIL THEN
  1303.               stat^.next:=statroot;
  1304.               statroot:=stat;
  1305.               done:=TRUE;
  1306.             ELSE
  1307.               stat^.next:=prev^.next;
  1308.               prev^.next:=stat;
  1309.               done:=TRUE;
  1310.             END;
  1311.           ELSE
  1312.             (* insert after current *)
  1313.             stat^.next:=cur^.next;
  1314.             cur^.next:=stat;
  1315.             done:=TRUE;
  1316.           END;
  1317.         ELSIF String.Compare(stat^.name,cur^.name)<0 THEN
  1318.           (* insert before current *)
  1319.           IF prev=NIL THEN
  1320.             stat^.next:=statroot;
  1321.             statroot:=stat;
  1322.             done:=TRUE;
  1323.           ELSE
  1324.             stat^.next:=prev^.next;
  1325.             prev^.next:=stat;
  1326.             done:=TRUE;
  1327.           END;
  1328.         ELSE
  1329.           prev:=cur;
  1330.           cur:=cur^.next;
  1331.         END;
  1332.         Break.TestBreak();
  1333.       UNTIL (cur=NIL) OR done;
  1334.       IF cur=NIL THEN
  1335.         prev^.next:=stat;
  1336.       END;
  1337.     END;
  1338.   END InsertStat;
  1339.  
  1340.  
  1341.  
  1342. (*---------------------------------------------------------------------------
  1343.   FindStat: Search linked list for a specific Statistics-record
  1344.             Create that record if not found
  1345.   ---------------------------------------------------------------------------*)
  1346. PROCEDURE FindStat(name: StringT; in: BOOLEAN): StatisticsPtr;
  1347.   VAR
  1348.     cur,next: StatisticsPtr;
  1349.     found: BOOLEAN;
  1350.   BEGIN
  1351.     next:=statroot;
  1352.     IF next#NIL THEN
  1353.       REPEAT
  1354.         cur:=next;
  1355.         next:=cur^.next;
  1356.         found:=(in=cur^.in) AND (String.Compare(name,cur^.name)=0);
  1357.         Break.TestBreak();
  1358.       UNTIL found OR (next=NIL);
  1359.     ELSE
  1360.       found:=FALSE;
  1361.     END;
  1362.     IF found THEN
  1363.       RETURN cur;
  1364.     ELSE
  1365.       next:=ExecL.AllocMem(SIZE(Statistics),ExecD.MemReqSet{});
  1366.       IF next=NIL THEN
  1367.         MyError("","not enough memory","",TRUE);
  1368.       END;
  1369.       ClearStat(next^);
  1370.       next^.name:=name;
  1371.       next^.in:=in;
  1372.       InsertStat(next);
  1373.       RETURN next;
  1374.     END;
  1375.   END FindStat;
  1376.  
  1377.  
  1378.  
  1379. (*---------------------------------------------------------------------------
  1380.   DisplayStatList: Display the entire list of Statistics-records
  1381.   ---------------------------------------------------------------------------*)
  1382. PROCEDURE DisplayStatList;
  1383.   VAR
  1384.     cur: StatisticsPtr;
  1385.   BEGIN
  1386.     cur:=statroot;
  1387.     WHILE cur#NIL DO
  1388.       IF (cur^.in AND args.in) OR ((NOT cur^.in) AND args.out) THEN
  1389.         DisplayStats(cur^);
  1390.       END;
  1391.       cur:=cur^.next;
  1392.     END;
  1393.   END DisplayStatList;
  1394.  
  1395.  
  1396.  
  1397. BEGIN
  1398.  SYSTEM.SETREG(11,SYSTEM.ADR(version));
  1399.  rdargsPtr:=NIL;
  1400.  xsroot:=NIL;
  1401.  statroot:=NIL;
  1402.  inf.fh:=NIL;
  1403.  stdErr:=NIL;
  1404.  
  1405.  IF (Arts.kickVersion<37) OR (DosL.dosVersion<37) THEN
  1406.   MyError("","You need OS 2.04 or later (dos.library V37 or later)",
  1407.           "",TRUE);
  1408.  END;
  1409.  
  1410.  Break.InstallException;
  1411.  PresetArgs();
  1412.  
  1413.  InOut.WriteString("\n\n"+vers+"\n");
  1414.  InOut.WriteString("© Copyright 1992,1993 by Jürgen Weinelt\n");
  1415. (*$ IF BETA *)
  1416.  InOut.WriteString("THIS IS A BETA VERSION! DO NOT DISTRIBUTE!\n\n");
  1417. (*$ ELSE *)
  1418.  InOut.WriteString("XStat is Freeware - read the docs for details.\n\n");
  1419. (*$ ENDIF *)
  1420.  
  1421.  IF NOT Arts.wbStarted THEN
  1422.   IF Arts.dosCmdLen>0 THEN
  1423.    temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
  1424.    IF temp[0]="?" THEN
  1425.     InOut.WriteString(extendedhelp);
  1426.     Arts.Exit(5);
  1427.    END;
  1428.   END;
  1429.  END;
  1430.  
  1431.  IF DosL.GetVar(SYSTEM.ADR("XSTATARGS"),SYSTEM.ADR(temp),
  1432.                 strlen,DosD.VarFlagSet{})#-1 THEN
  1433.   index:=String.Length(temp);
  1434.   IF temp[index-1]#"\n" THEN
  1435.     temp[index]:="\n"; temp[index+1]:="\o";
  1436.   END;
  1437.   ParseDosArgs(temp);
  1438.  END;
  1439.  
  1440.  IF NOT Arts.wbStarted THEN
  1441.   IF Arts.dosCmdLen>0 THEN
  1442.    temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
  1443.    ParseDosArgs(temp);
  1444.   END;
  1445.  END;
  1446.  
  1447.  IF DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(args.to))<0 THEN
  1448.   MyError("","illegal combination of parameters",
  1449.           "FROMDATE is later than TODATE",TRUE);
  1450.  END;
  1451.  IF NOT (args.in OR args.out) THEN
  1452.   MyError("","illegal combination of parameters",
  1453.           "you can't specify NOINCOM and NOOUTGO simultaneously",TRUE);
  1454.  END;
  1455.  
  1456.  IF args.stderr THEN
  1457.    stdErr:=DosL.Open(SYSTEM.ADR("CONSOLE:"),DosD.newFile);
  1458.    IF stdErr=NIL THEN
  1459.      MyError("ERROR:","can't open CONSOLE: for StdErr output",
  1460.              "guess you'll have to live without it :-)",FALSE);
  1461.    END;
  1462.  END;
  1463.  
  1464.  ClearStat(totali);
  1465.  totali.in:=TRUE;
  1466.  ClearStat(totalo);
  1467.  totalo.in:=FALSE;
  1468.  
  1469.  inf.fh:=DosL.Open(SYSTEM.ADR(args.xstatdata),DosD.readOnly);
  1470.  IF inf.fh=NIL THEN
  1471.   MyError("Can't open XStat.data!","filename was",args.xstatdata,TRUE);
  1472.  END;
  1473.  GetXStatData();
  1474.  DosL.Close(inf.fh);
  1475.  IF currency[0]=ASCII.nul THEN
  1476.   MyError("WARNING","no currency definition found in XStat.data",
  1477.           "using 'DM' as default",FALSE);
  1478.   currency:=" DM";
  1479.  END;
  1480.  
  1481.  inf.fh:=DosL.Open(SYSTEM.ADR(args.xferstat),DosD.readOnly);
  1482.  IF inf.fh=NIL THEN
  1483.   MyError("Can't open Xferstat!","filename was",args.xferstat,TRUE);
  1484.  END;
  1485.  
  1486.  REPEAT
  1487.   Break.TestBreak();
  1488.   GetLog(ln1,ln2);
  1489.   IF String.Length(ln1)+String.Length(ln2)>0 THEN
  1490.    IF ParseLog(ln1,ln2,log) THEN
  1491.     IF (args.host[0]=ASCII.nul) OR
  1492.        (DosL.MatchPatternNoCase(SYSTEM.ADR(args.host),SYSTEM.ADR(log.host))) THEN
  1493.      IF (DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(log.sdate))>=0) AND
  1494.         (DosL.CompareDates(SYSTEM.ADR(log.sdate),SYSTEM.ADR(args.to))>=0) THEN
  1495.       IF ComputeCost(log) THEN
  1496.        IF args.verbose THEN
  1497.         DisplayConnect(log);
  1498.        END;
  1499.  
  1500.        IF args.perhost THEN
  1501.          IF NOT log.ignore THEN
  1502.            UpdateStat(FindStat(log.host,NOT log.callout)^,log);
  1503.          END;
  1504.        END;
  1505.        IF log.callout THEN
  1506.          UpdateStat(totalo,log);
  1507.        ELSE
  1508.          UpdateStat(totali,log);
  1509.        END;
  1510.  
  1511.       ELSE
  1512.        IF NOT args.quiet THEN
  1513.         InOut.WriteString("(ignoring this one)\n\n\n");
  1514.        END;
  1515.       END;
  1516.      END;
  1517.     END;
  1518.    ELSE
  1519.     IF NOT args.quiet THEN
  1520.      InOut.WriteString("(ignoring this one)\n\n\n");
  1521.     END;
  1522.    END;
  1523.   END;
  1524.  UNTIL inf.eof;
  1525.  
  1526.  IF args.perhost THEN
  1527.    DisplayStatList;
  1528.  END;
  1529.  IF args.in THEN
  1530.   DisplayStats(totali);
  1531.  END;
  1532.  IF args.out THEN
  1533.   DisplayStats(totalo);
  1534.  END;
  1535. CLOSE
  1536.   IF rdargsPtr#NIL THEN
  1537.     DosL.FreeArgs(rdargsPtr);
  1538.     DosL.FreeDosObject(DosD.dosRdArgs,rdargsPtr);
  1539.     rdargsPtr:=NIL;
  1540.   END;
  1541.   IF xsroot#NIL THEN
  1542.     FreeXData(xsroot);
  1543.     xsroot:=NIL;
  1544.   END;
  1545.   IF statroot#NIL THEN
  1546.     FreeStat(statroot);
  1547.     statroot:=NIL;
  1548.   END;
  1549.   IF inf.fh#NIL THEN
  1550.     DosL.Close(inf.fh);
  1551.     inf.fh:=NIL;
  1552.   END;
  1553.   IF stdErr#NIL THEN
  1554.     DosL.Close(stdErr);
  1555.     stdErr:=NIL;
  1556.   END;
  1557. END XStat.
  1558.